#include <iostream>
#include <fstream>
#include <stdlib.h>
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"

using namespace std;
using namespace glm;

typedef struct Pigment {
public:
    float r;
    float g;
    float b;
    float f;

    Pigment() {
        r = 0.0;
        g = 0.0;
        b = 0.0;
        f = 0.0;
    }

    void print() {
        printf("\tColor: %f %f %f %f\n", 
                r, g, b, f);
    }
} MPigment;

typedef struct Finish {
public:
    float ambient;
    float diffuse;
    float specular;
    float roughness;
    float reflection;
    float refraction;
    float ior;
    Finish() {
        ambient = 0.1;
        diffuse = 0.6;
        specular = 0.0;
        roughness = 0.05;
        reflection = 0.0;
        refraction = 0.0;
        ior = 1.0;
    }

    void print() {
        printf("\tFinish:\n"
            "\t\tAmbient: %f\n\t\tDiffuse: %f\n\t\tSpecular: %f\n\t\t"
            "Rougness: %f\n\t\tReflection: %f\n\t\tRefraction: %f\n\t\t"
            "IOR: %f\n",
            ambient, diffuse, 
            specular, roughness,
            reflection, refraction,
            ior);
    }
} MFinish;

class Ray {
public:
    vec3 point, d;
    vec4 color;
    float t;
};

class Object {
    virtual Symbol parse(ifstream &file, Symbol curToken) {
    }
    
    virtual void print() {
    }
};

class WCamera:public Object {
public:
    vec3 location;
    vec3 up;
    vec3 right;
    vec3 lookAt;

    Symbol parse(ifstream &file, Symbol curToken);
    void print();
};

class WLight:public Object{
public:
    vec3 location;
    vec3 color;

    Symbol parse(ifstream &file, Symbol curToken);
    void print();
};

class Geometry: public Object {
public:
    MFinish finish;
    MPigment pigment;
    mat4 transform;
    string name;
    Geometry() {
        transform = mat4(1.0f);
    }

    virtual Symbol parse(ifstream &file, Symbol curToken) {
    }

    virtual bool intersect(Ray *r) { };
    virtual vec3 getNormal(Ray *r) {};

    virtual void print() {
        printf("\tTransform:\n\t%f\t%f\t%f\t%f\n\t%f\t%f\t%f\t%f\n\t"
        "%f\t%f\t%f\t%f\n\t%f\t%f\t%f\t%f\n",
        transform[0][0], transform[1][0],
        transform[2][0], transform[3][0],
        transform[0][1], transform[1][1],
        transform[2][1], transform[3][1],
        transform[0][2], transform[1][2],
        transform[2][2], transform[3][2],
        transform[0][3], transform[1][3],
        transform[2][3], transform[3][3]);

        finish.print();
        pigment.print();
    };

    string getName() {
        return name;
    }
};

class WSphere: public Geometry {
public:
    vec3 center;
    float radius;

    WSphere() {
        name = "Sphere";
    }

    Symbol parse(ifstream &file, Symbol curToken);
    bool intersect(Ray *r);
    vec3 getNormal(Ray *r);

    void print() {
        printf("Sphere:\n\tCenter: %f %f %f\n\tRadius: %f\n",
            center.x, center.y, center.z, radius);

        Geometry::print();
    }
};

class WPlane: public Geometry {
public:
    vec3 normal;
    vec3 pointOnPlane;
    float distance;

    WPlane() {
        name = "Plane";
    }

    Symbol parse(ifstream &file, Symbol curToken);
    bool intersect(Ray *r);
    vec3 getNormal(Ray *r);

    void print() {
        printf("Plane:\n\tNormal: %f %f %f\n\tDistance: %f\n",
            normal.x, normal.y, normal.z, distance);

        Geometry::print();
    }
};

class WTriangle: public Geometry {
public:
    vec3 p1, p2, p3;

    WTriangle() {
        "Triangle";
    }

    Symbol parse(ifstream &file, Symbol curToken);
    bool intersect(Ray *r);
    vec3 getNormal(Ray *r);

    void print() {
        printf("Triangle:\n\tCorner 1: %f %f %f\n\tCorner 2: %f %f %f"
                "\n\tCorner3: %f %f %f\n",
                p1.x, p1.y, p1.z,
                p2.x, p2.y, p2.z,
                p3.x, p3.y, p3.z);

        Geometry::print();
    }
};

class WCone: public Geometry {
public:
    vec3 base, top;
    float baseR, topR;

    WCone() {
        name = "Cone";
    }

    Symbol parse(ifstream &file, Symbol curToken);
    bool intersect(Ray *r);
    vec3 getNormal(Ray *r);

    void print() {
        printf("Cone:\n\tBase: %f %f %f\n\t\tBase Radius: %f"
               "\n\tTop: %f %f %f\n\t\tTop Radius: %f\n",
               base.x, base.y, base.z, baseR,
               top.x, top.y, top.z, topR);

        Geometry::print();
    }
};

class WBox: public Geometry {
public:
    vec3 min, max;

    WBox() {
        name = "Box";
    }

    Symbol parse(ifstream &file, Symbol curToken);
    bool intersect(Ray *r);
    vec3 getNormal(Ray *r);

    void print() {
        printf("Box:\n\tMin: %f %f %f\n\tMax: %f %f %f\n",
                min.x, min.y, min.z, max.x, max.y, max.z);

        Geometry::print();
    }
};

class Scene {
public:
    WCamera *camera;
    LinkedList<WLight> lights;
    LinkedList<Geometry> geometry;

    void debug() {
        camera->print();

        lights.reset();
        while (lights.hasNext()) {
            lights.getNext()->print();
        }

        geometry.reset();
        while (geometry.hasNext()) {
            geometry.getNext()->print();
        }
    }
};

